<script setup lang="ts">
import { FormInfo, ChartResults } from "@/views/resourceAccess/components";
import { chartResults, formInfo } from "../components/interface";
import { reactive, ref, watch } from "vue";
import { http } from "@/utils/http";
import dataMock from "./assets/dataMock.json";
import { socket } from "./socket";
import PARAM_OPT_MOCK from "./assets/edgeCloud4/FedABS_AlexNet_record.json";
import PARAM_BASIC_MOCK from "./assets/edgeCloud4/FedDBS_AlexNet_record.json";
const formProps: formInfo = reactive({
  title: "参数优化",
  btnNames: ["开始优化", "暂停", "结束"],
  btnDisabled: [false, false, false],
  btnLoading: [false, false, false],
  formInfoList: [
    {
      id: 1,
      title: "待处理任务选择",
      inputType: "0",
      options: [
        {
          value: JSON.stringify(["AlexNet", "CIFAR10"]),
          label: "AlexNet&CIFAR10"
        },
        {
          value: JSON.stringify(["CNN", "EMNIST"]),
          label: "CNN&EMNIST"
        },
        {
          value: JSON.stringify(["VGG", "image100"]),
          label: "VGG16&IMAGE100"
        }
      ]
    },
    {
      id: 2,
      title: "配置文件",
      inputType: "1",
      options: undefined
    },
    {
      id: 3,
      title: "基准训练方法",
      inputType: "0",
      options: [
        {
          value: "FedDBS",
          label: "FedDBS"
        },
        {
          value: "FedAvg",
          label: "FedAvg"
        }
      ]
    },
    {
      id: 4,
      title: "优化训练方法",
      inputType: "0",
      options: [
        {
          value: "FedABS",
          label: "FedABS"
        }
      ]
    }
  ]
});
const chartResultsProps: chartResults = reactive({
  header: ["epoch1:  -142.1546", "epoch1:  -82.1546"],
  result: "优化训练方法比基准训练方法在相同准确率下至少节省30%训练时间",
  chartSelect: [0, 0],
  chartData: [
    {
      title: "模型精度",
      legend: ["基准训练方法", "优化训练方法"],
      xAxisName: "",
      xAxisData: ["20", "40", "80", "100", "120", "140", "160", "180", "200"],
      yAxisSetting: {
        min: 0,
        max: 1,
        interval: 0.2
      },
      seriesData: [
        {
          name: "基准训练方法",
          data: [0.6, 0.79, 0.4, 0.8, 0.65]
        },
        {
          name: "优化训练方法",
          data: [0.45, 0.35, 0.6, 0.55, 0.75]
        }
      ]
    },
    {
      title: "资源消耗",
      legend: ["基准训练方法", "优化训练方法"],
      xAxisName: "",
      xAxisData: ["20", "40", "80", "100", "120", "140", "160", "180", "200"],
      yAxisSetting: {},
      seriesData: [
        {
          name: "基准训练方法",
          data: [2, 2.3, 1.6, 2.41, 2.1]
        },
        {
          name: "优化训练方法",
          data: [1.7, 1.5, 2, 1.9, 2.3]
        }
      ]
    }
  ]
});

const initStaticChart = (dataStatic1, dataStatic2) => {
  const xData = [],
    seriesData0 = [[], []],
    seriesData1 = [[], []];
  dataStatic1.forEach(item => {
    xData.push(item.epoch);
    seriesData0[0].push(Number(item.accuracy));
    seriesData1[0].push(Number(item.time));
  });
  dataStatic2.forEach(item => {
    seriesData0[1].push(Number(item.accuracy));
    seriesData1[1].push(Number(item.time));
  });

  const result1 = dataStatic1[dataStatic1.length - 1];
  const result2 = dataStatic2[dataStatic2.length - 1];
  const save = (
    ((Number(result1.time) - Number(result2.time)) * Number(100)) /
    Number(result1.time)
  ).toFixed(2);

  chartResultsProps.header = [
    `epoch${result1.epoch}: accuracy ${Number(result1.accuracy).toFixed(4)}`,
    `epoch${result2.epoch}: accuracy ${Number(result2.accuracy).toFixed(4)}`
  ];
  chartResultsProps.chartData[0].xAxisData = xData;
  chartResultsProps.chartData[1].xAxisData = xData;
  chartResultsProps.chartData[0].seriesData[0]["data"] = seriesData0[0];
  chartResultsProps.chartData[0].seriesData[1]["data"] = seriesData0[1];
  chartResultsProps.chartData[1].seriesData[0]["data"] = seriesData1[0];
  chartResultsProps.chartData[1].seriesData[1]["data"] = seriesData1[1];
  chartResultsProps.result = `优化训练方法比基准训练方法在相同准确率下至少节省${save}%的资源开销`;
};

initStaticChart(PARAM_BASIC_MOCK, PARAM_OPT_MOCK);

// 是否仿真
let mock = false;
let waitTime = 1000;
const mockBasicStop = ref(false),
  mockOptStop = ref(false);
let basicRendering = false,
  optRendering = false;
// 变量
let chartResBasic = [],
  chartResOpt = [],
  xAData = [];
let hasStart = false;
const receiveData = ref(false);
// 多次暂停标志
let basicShouldStop = false,
  optShouldStop = false;
let basicTimer1 = null,
  basicTimer2 = null,
  optTimer1 = null,
  optTimer2 = null;
const hasPauseBasic = ref(false),
  hasPauseOpt = ref(false),
  hasStop = ref(false);
const renderNumBasic = ref(-1),
  renderNumOpt = ref(-1);
const pause = ref(false);
const sleep = (ms: number) => {
  return new Promise(resolve => setTimeout(resolve, ms));
};
// 开始渲染
const startRender = async () => {
  basicShouldStop = false;
  optShouldStop = false;
  // 需要判断当前数据是否为最新
  // 若为旧数据
  // 计算当前渲染距离下一次渲染需要多少时间
  basicTimer1 = setTimeout(async () => {
    while (
      renderNumBasic.value < chartResBasic.length - 1 &&
      !basicShouldStop
    ) {
      // 计算当前渲染距离下一次渲染需要多少时间
      let bTime =
        (chartResBasic[renderNumBasic.value + 1].time -
          chartResBasic[renderNumBasic.value].time) *
        waitTime;
      basicTimer2 = setTimeout(() => {
        basicRendering = true;
        renderNumBasic.value++;
        renderChart(chartResBasic, 0, renderNumBasic.value);
        basicRendering = false;
      }, bTime);
      await sleep(bTime);
      // 如果更新到最新数据，则继续执行监听端口的数据
      if (renderNumBasic.value == chartResBasic.length - 1) {
        hasPauseBasic.value = false;
      }
    }
  }, 500);
  optTimer1 = setTimeout(async () => {
    while (renderNumOpt.value < chartResOpt.length - 1 && !optShouldStop) {
      let oTime =
        (chartResOpt[renderNumOpt.value + 1].time -
          chartResOpt[renderNumOpt.value].time) *
        waitTime;
      optTimer2 = setTimeout(() => {
        optRendering = true;
        renderNumOpt.value++;
        renderChart(chartResOpt, 1, renderNumOpt.value);
        optRendering = false;
      }, oTime);
      await sleep(oTime);
      if (renderNumOpt.value == chartResOpt.length - 1) {
        hasPauseOpt.value = false;
      }
    }
  }, 500);
};

// 暂停渲染
const pauseRender = () => {
  clearTimeout(basicTimer1);
  clearTimeout(basicTimer2);
  clearTimeout(optTimer1);
  clearTimeout(optTimer2);
  basicShouldStop = true;
  optShouldStop = true;
  renderResult();
};
// 终止渲染
const stopRender = () => {
  hasStart = false;
  pauseRender();
};
const stopSocket = () => {
  socket.off("parameter_optimization_intermediate_result");
  socket.off("parameter_optimization_end_train_confirm");
};
// 渲染表格
const renderChart = (dataArr: any, flag: number, curNum: number) => {
  console.log(dataArr[curNum]);
  // flag:0-basic,flag:1-opt,
  if (!chartResultsProps.chartData[0].xAxisData.includes(xAData[curNum])) {
    chartResultsProps.chartData[0].xAxisData.push(xAData[curNum]);
    chartResultsProps.chartData[1].xAxisData.push(xAData[curNum]);
  }
  chartResultsProps.header[flag] =
    `epoch${dataArr[curNum].epoch}: accuracy ${dataArr[curNum].accuracy}`;
  chartResultsProps.chartData[0].seriesData[flag]["data"].push(
    dataArr[curNum].accuracy
  );
  chartResultsProps.chartData[1].seriesData[flag]["data"].push(
    dataArr[curNum].time
  );
};

// 渲染结果
const renderResult = () => {
  if (chartResBasic.length != 0 && chartResOpt.length != 0) {
    const minEpoch =
      renderNumBasic.value > renderNumOpt.value
        ? renderNumOpt.value
        : renderNumBasic.value;
    const bTime = chartResBasic[minEpoch].time,
      oTime = chartResOpt[minEpoch].time;
    const save = (((bTime - oTime) * 100) / bTime).toFixed(2);
    chartResultsProps.result = `优化训练方法比基准训练方法在相同准确率下至少节省${save}%的资源开销`;
  }
};
// 初始化数据
const initChartData = () => {
  chartResultsProps.header = ["准备中...", "准备中..."];
  chartResultsProps.chartData[0].xAxisData = [];
  chartResultsProps.chartData[1].xAxisData = [];
  chartResultsProps.chartData[0].seriesData[0]["data"] = [];
  chartResultsProps.chartData[0].seriesData[1]["data"] = [];
  chartResultsProps.chartData[1].seriesData[0]["data"] = [];
  chartResultsProps.chartData[1].seriesData[1]["data"] = [];
  chartResultsProps.result = "";
  chartResBasic = [];
  chartResOpt = [];
  xAData = [];
  hasStart = false;
  basicShouldStop = false;
  optShouldStop = false;
  basicTimer1 = null;
  basicTimer2 = null;
  optTimer1 = null;
  optTimer2 = null;
  receiveData.value = false;
  hasPauseBasic.value = false;
  hasPauseOpt.value = false;
  renderNumBasic.value = -1;
  renderNumOpt.value = -1;
  pause.value = false;
};
const initMockData = () => {
  mockBasicStop.value = false;
  mockOptStop.value = false;
  basicRendering = false;
  optRendering = false;
};
// 开始时的按钮状态
const startBtnStatus1 = () => {
  formProps.btnDisabled[1] = true;
  formProps.btnDisabled[2] = true;
};
const startBtnStatus2 = () => {
  formProps.btnDisabled[1] = false;
  formProps.btnDisabled[2] = false;
};
// 终止时的按钮状态
const stopBtnStatus1 = () => {
  formProps.btnLoading = [false, false, true];
  formProps.btnDisabled = [true, true, false];
  formProps.btnNames[2] = "结束中...";
};
const stopBtnStatus2 = () => {
  formProps.btnLoading = [false, false, false];
  formProps.btnDisabled = [false, false, false];
  formProps.btnNames = ["开始卸载", "暂停", "结束"];
};
const btnClick1 = async e => {
  initChartData();
  startBtnStatus1();
  hasStart = true;
  if (mock) {
    initMockData();
    const mock_length = PARAM_OPT_MOCK.length;
    setTimeout(async () => {
      for (let i = 0; i < mock_length; i++) {
        const data = PARAM_BASIC_MOCK[i];
        if (i == 0) {
          await sleep(Number(data["time"]) * waitTime);
        } else {
          await sleep(
            (Number(PARAM_BASIC_MOCK[i]["time"]) -
              Number(PARAM_BASIC_MOCK[i - 1]["time"])) *
              waitTime
          );
        }
        if (!hasStart) break;
        // 判断是否存在最新的epoch
        if (!xAData.includes(data["epoch"])) {
          xAData.push(data["epoch"]);
        }
        let res = {
          epoch: data["epoch"],
          time: Number(data["time"]),
          accuracy: Number(data["accuracy"]).toFixed(4)
        };
        chartResBasic.push(res);
        if (!hasPauseBasic.value && !basicShouldStop) {
          renderNumBasic.value++;
          renderChart(chartResBasic, 0, renderNumBasic.value);
        }
        if (i == mock_length - 1) {
          mockBasicStop.value = true;
        }
      }
    }, 500);
    setTimeout(async () => {
      for (let i = 0; i < mock_length; i++) {
        const data = PARAM_OPT_MOCK[i];
        if (i == 0) {
          await sleep(Number(data["time"]) * waitTime);
        } else {
          await sleep(
            (Number(PARAM_OPT_MOCK[i]["time"]) -
              Number(PARAM_OPT_MOCK[i - 1]["time"])) *
              waitTime
          );
        }
        if (!hasStart) break;
        // 判断是否存在最新的epoch
        if (!xAData.includes(data["epoch"])) {
          xAData.push(data["epoch"]);
        }
        let res = {
          epoch: data["epoch"],
          time: Number(data["time"]),
          accuracy: Number(data["accuracy"]).toFixed(4)
        };
        chartResOpt.push(res);
        if (!hasPauseOpt.value && !optShouldStop) {
          renderNumOpt.value++;
          renderChart(chartResOpt, 1, renderNumOpt.value);
        }
        if (i == mock_length - 1) {
          mockOptStop.value = true;
        }
      }
    }, 500);
  } else {
    const task = JSON.parse(e[1][0].value);
    const params = {
      about: "parameter_optimization",
      model_type: task[0],
      dataset_type: task[1],
      config: e[1][1].value,
      baseline: e[1][2].value,
      optimal: e[1][3].value
    };
    /*update_data = {
      'about': 'parameter_optimization', 
      'is_optimal': 1, 
      'is_end': 0, 
      'model': 'FedABS', 
      'epoch': 1, 
      'accuracy': 0.1, 
      'time': 1
    }*/
    socket.on("parameter_optimization_intermediate_result", data => {
      console.log(data);

      if (!receiveData.value) {
        startBtnStatus2();
        receiveData.value = true;
      }
      const epoch = data["epoch"].toString();
      // 判断是否存在最新的epoch
      if (!xAData.includes(epoch)) {
        xAData.push(epoch);
      }
      let res = {
        epoch: epoch,
        time: data["total_time"],
        accuracy: data["accuracy"].toFixed(4)
      };
      // 渲染数据
      if (data["is_optimal"] == 0) {
        chartResBasic.push(res);
        // 发生暂停说明渲染数据与最新数据存在延迟
        // basicShouldStop：若恰巧最后一次暂停时渲染的数据与当前同步，那么下一次渲染时若未点继续则应暂停
        if (!hasPauseBasic.value && !basicShouldStop) {
          // renderNumBasic记录当前basic渲染的位置
          renderNumBasic.value++;
          renderChart(chartResBasic, 0, renderNumBasic.value);
        }
      } else if (data["is_optimal"] == 1) {
        chartResOpt.push(res);
        if (!hasPauseOpt.value && !optShouldStop) {
          renderNumOpt.value++;
          renderChart(chartResOpt, 1, renderNumOpt.value);
        }
      }
      console.log("parameter_optimization_intermediate_result", data);
    });
    socket.on("parameter_optimization_end_train_confirm", data => {
      // 1.不存在暂停事件则直接终止
      // 2.当前数据已更新为最新数据说明暂停造成的延迟失效，可终止
      if (!hasPauseBasic.value && !hasPauseOpt.value) {
        stopRender();
        stopSocket();
        stopBtnStatus2();
      }
      // 后端终止标志
      hasStop.value = true;
      console.log("parameter_optimization_end_train_confirm1", data);
    });
    socket.emit("start_train", params, res => {
      console.log("parameter_optimization_training_start", params);
    });
  }
  formProps.btnLoading[0] = true;
  formProps.btnNames[0] = "优化中...";
};

const btnClick2 = async e => {
  // 暂停状态为true，点击按钮
  // 未开始时不能够点击
  if (hasStart) {
    formProps.btnLoading[1] = true;
    // 若为暂停，则点击继续
    if (pause.value) {
      formProps.btnNames[1] = "继续中...";
      await sleep(3000);
      formProps.btnNames[1] = "暂停";
      startRender();
    } else {
      // 若为继续，则点击暂停
      formProps.btnNames[1] = "暂停中...";
      pauseRender();
      await sleep(3000);
      formProps.btnNames[1] = "继续";
    }
    formProps.btnLoading[1] = false;
    hasPauseBasic.value = true;
    hasPauseOpt.value = true;
    pause.value = !pause.value;
  }
};
const btnClick3 = async e => {
  stopBtnStatus1();
  stopRender();
  socket.on("parameter_optimization_end_train_confirm", data => {
    stopSocket();
    stopBtnStatus2();
    console.log("parameter_optimization_end_train_confirm2", data);
  });
  if (!mock) {
    socket.emit(
      "end_train",
      {
        about: "parameter_optimization",
        baseline: e[1][2].value,
        optimal: e[1][3].value
      },
      data => {
        console.log("end_train", data);
      }
    );
  }
};
watch(
  () => [hasStop.value, hasPauseBasic.value, hasPauseOpt.value],
  (newVal, oldVal) => {
    console.log(hasStop.value, hasPauseBasic.value, hasPauseOpt.value);
    console.log(newVal, oldVal);
    if (newVal[0] && !newVal[1] && !newVal[2]) {
      console.log("stop");
      stopRender();
      stopSocket();
      stopBtnStatus2();
    }
  }
);
// 暂停后的Mock操作
watch(
  () => [
    mockBasicStop.value,
    mockOptStop.value,
    hasPauseBasic.value,
    hasPauseOpt.value
  ],
  (newVal, oldVal) => {
    if (newVal[0] && newVal[1] && !newVal[2] && !newVal[3]) {
      console.log("stop");
      stopRender();
      stopBtnStatus2();
    }
  }
);
</script>

<template>
  <el-row :gutter="20">
    <el-col :span="12" style="min-height: 200px">
      <el-col style="min-height: 200px">
        <FormInfo
          :title="formProps.title"
          :btnNames="formProps.btnNames"
          :formInfoList="formProps.formInfoList"
          :btnDisabled="formProps.btnDisabled"
          :btnLoading="formProps.btnLoading"
          @btnClick1="btnClick1"
          @btnClick2="btnClick2"
          @btnClick3="btnClick3"
        />
      </el-col>
    </el-col>
    <el-col :span="12" style="min-height: 200px">
      <el-col style="min-height: 200px">
        <ChartResults
          :header="chartResultsProps.header"
          :chartSelect="chartResultsProps.chartSelect"
          :chartData="chartResultsProps.chartData"
          :result="chartResultsProps.result"
        />
      </el-col>
    </el-col>
  </el-row>
</template>
